﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Xml.Linq;
using System.Xml.Serialization;
using VMS.FTS.APPPUSH.Log;
using VMS.FTS.APPPUSH.Plugins;

namespace VMS.FTS.APPPUSH.Device
{
    public class DeviceManager :ILogable  ,IDisposable
    {
        #region | Singleton construction |
        private static readonly Lazy<DeviceManager> lazy =
            new Lazy<DeviceManager>(() => new DeviceManager());

        public static DeviceManager Instance { get { return lazy.Value; } }
        #endregion

        #region | Constructors |
        /// <summary>
        /// Default constructor
        /// </summary>
        public DeviceManager()
        {
            // Init logger
            LOG_TAG = this.GetType().Name;// "VMS Device Manager";
            DATABASE_NAME = Properties.Settings.Default.WORKING_DIRECTORY +
               Properties.Settings.Default.DATABASE_DIRECTORY + 
               Properties.Settings.Default.DATABASE_NAME;
            
        }
        
        #endregion 

        #region | Network |
        /// <summary>
        /// This function spawns new thread for TCP communication
        /// </summary>
        public void InitNetwork()
        {
            LOG(MethodBase.GetCurrentMethod().Name, "Init network...", LOG_LEVEL.INFO);
            ManagerThread = new Thread(new ThreadStart(StartListen));
            ManagerThread.Start();
        }

        /// <summary>
        /// Server listens on the given port and accepts the connection from Client.
        /// </summary>
        public void StartListen()
        {
            // create new tcplistener object at default port.
            DeviceListener = new TcpListener(DEFAULT_PORT);
            DeviceListener.Start(); // start listenning
            IPHostEntry host;
            string localIP = "";
            host = Dns.GetHostEntry(Dns.GetHostName());
            foreach (IPAddress ip in host.AddressList)
            {
                if (ip.AddressFamily == AddressFamily.InterNetwork)
                {
                    localIP += ip.ToString() + " | ";
                }
            }
            LOG(MethodBase.GetCurrentMethod().Name, "Start listenning at port: " + DEFAULT_PORT + " Local Ip: " + localIP, LOG_LEVEL.INFO);

            isStarted = true;
            
            // Keep on accepting Client Connection
            while (isStarted)
            {
                // New Client connected, call Event to handle it.
                Thread t = new Thread(new ParameterizedThreadStart(NewClient));
                
                // Waiting for new client connected and start new thread
                try
                {
                    t.Start(DeviceListener.AcceptTcpClient());
                }
                catch (Exception ex)
                { }         
            }

        }

        public void NewClient(Object obj)
        {
            TcpClient tcpClient = (TcpClient)obj;
            if (tcpClient == null)
                return;

            // Network stream
            NetworkStream ns = tcpClient.GetStream();
            // Buffer for check IMEI;
            byte[] data = new byte[17];
            StringBuilder dataString = new StringBuilder();
            string remoteIP = ((IPEndPoint)tcpClient.Client.RemoteEndPoint).Address.ToString();
            string remotePort = ((IPEndPoint)tcpClient.Client.RemoteEndPoint).Port.ToString();
            
            int readCount;
            try
            {
                while ((readCount = ns.Read(data, 0, 17)) != 0)
                {
                    if (bAcceptIMEI(data))
                    {
                        dataString.Append(Encoding.ASCII.GetString(data, 2, 15));
                        if (tcpClient != null)
                        {
                            LOG(remoteIP + "\\" + remotePort + " IMEI: " + dataString.ToString(), LOG_LEVEL.INFO);
                            
                            // look for device with imei
                            try
                            {
                                foreach (VMSDevice dev in _devices)
                                {
                                    if (dev.IMEI == dataString.ToString())
                                    {
                                        //lock (dev)
                                        {
                                            // open device
                                            
                                            dev.open(tcpClient);
                                        }
                                    }
                                }
                            }
                            catch (Exception e)
                            {
                                LOG(e.Message, LOG_LEVEL.ERROR);
                            }
                            
                        }
                        break;
                    }
                }
            }
            catch (Exception ex)
            {
                LOG(remoteIP + "\\" + remotePort + " Read IMEI time out!", LOG_LEVEL.INFO);
                tcpClient.Close();
                Debug.WriteLine("MainForm: " + ex.Message);
                Debug.WriteLine(ex.StackTrace);
            }
        }

        private bool bAcceptIMEI(byte[] btReceive)
        {
            if (btReceive[0] == 0x01 && // Device tag
                btReceive[1] == 0xD0 &&
                btReceive.Length == 17)
            {
                return true;
            }
            else return false;
        }

        /// Function to stop the TCP communication. It kills the thread   and closes client connection
        public void StopServer()
        {
            if (DeviceListener != null && _devices != null)
            {
                foreach (VMSDevice dev in _devices)
                {
                    lock (dev)
                    {
                        dev.close();
                    }
                }

                DeviceListener.Stop();
                // Abort Listening Thread and Stop listening
                if (ManagerThread.IsAlive)
                    ManagerThread.Abort();
                
                LOG(MethodBase.GetCurrentMethod().Name, "Server stopped",LOG_LEVEL.INFO);
                // TO DO:
                // change devices status to offline

            }
        }
        #endregion
        
        #region | Methods |
        /// <summary>
        /// Get list managed device.
        /// </summary>
        public List<string> GetAvaibleDevice()
        {
            return _devices.Select(x => x.ID).ToList();
        }

        public VMSDevice GetDeviceByID(string vmsID)
        {
                return _devices.Find(d => d.ID.Equals( vmsID));
                
            /*
            foreach (VMSDevice dev in _devices)
                if (dev.ID == vmsID)
                {
                    if (dev.Info.Type == "LETI")
                        return (LETIDevice)dev;
                    else
                        return dev;
                }  
            return null;*/
        }
        /// <summary>
        /// Load device info from local database
        /// </summary>
        public void AddDevice(VMSDevice dev)
        {
            _devices.Add(dev);
        }

        /// <summary>
        /// Load device info from local database
        /// </summary>
        public void AddDevices(List<VMSDevice> devs)
        {
            _devices.AddRange(devs);
        }

        #endregion

        #region | Local database |
        // Database name
        private string _DATABASE_NAME = @"";
        public string DATABASE_NAME
        {
            get { return _DATABASE_NAME; }
            set
            {
                LOG(MethodBase.GetCurrentMethod().Name, "Init local database...", LOG_LEVEL.INFO);
                _DATABASE_NAME = value;
                // Create database if not exists
                if (!File.Exists(_DATABASE_NAME))
                {
                    _DATABASE_NAME = "";
                    return;
                }

                try
                {
                    // Load database from file.
                    ///rootElement = XElement.Load(DATABASE_NAME);
                    //
                    
                    // Get supported device type 
                    List<Type> typeList = PluginsManager.Instance.GetSupportedDeviceType();

                    // Serialize the order to a file.   
                    XmlSerializer serializer = new XmlSerializer(typeof(MemoDatamodel), typeList.ToArray());

                    // Normalize xml file
                    NormalizeXmlFile( typeList);

                    // Deserialize data from xml
                    FileStream fs = new FileStream(DATABASE_NAME, FileMode.Open);
                    MemoDatamodel catalog = (MemoDatamodel)serializer.Deserialize(fs);

                    // check null
                    if (_devices == null)
                        _devices = new List<VMSDevice>();
                    else
                        _devices.Clear();
                                                                
                    foreach (VMSDevice dev in catalog.Devices)
                    {
                        //if (dev.Info.Type == "LETI")
                        //    _devices.Add(new LETIDevice(dev)); 
                        //else
                            _devices.Add(dev);
                    }

                    foreach (VMSImage img in catalog.Images)
                        Console.Out.WriteLine(img.ToString());
                    ///serializer.Serialize(Console.Out, catalog);
                    fs.Close();
                                                                 
                }
                catch (Exception e)
                {
                    _DATABASE_NAME = "";
                    LOG(e.Message , LOG_LEVEL.ERROR);
                    LOG(e.InnerException.Message, LOG_LEVEL.ERROR);
                    return;
                }
                LOG(MethodBase.GetCurrentMethod().Name, "   Local Database ready!", LOG_LEVEL.INFO);
            }
        }

        /// <summary>
        /// Remove un supported device type in xml file
        /// </summary>
        /// <param name="typeList"></param>
        private void NormalizeXmlFile(List<Type> typeList)
        {
            var root =  XDocument.Load(DATABASE_NAME);
            IEnumerable<XElement> devices = root.Descendants("VMSDevice");
            List<XElement> removeList = new List<XElement>();

            // get list of unknow device type
            foreach (XElement devElement in devices)
            {
                try
                {
                    string devType = devElement.Attribute(XName.Get("type", "http://www.w3.org/2001/XMLSchema-instance")).Value;
                    Type type = typeList.Find(t => t.Name == devType);
                    if (type == null)
                    {
                        removeList.Add(devElement);
                    }
                }catch (NullReferenceException nEx)
                {
                    removeList.Add(devElement);
                }
            }

            // remove unknow device node
            foreach (XElement el in removeList)
            {
                el.Remove();
            }

            // save 
            root.Save(DATABASE_NAME);
        }

        [XmlRoot("LedCenter")]
        public class MemoDatamodel
        {
            [XmlArray("VMSDevices")]
            [XmlArrayItem("VMSDevice")]
            public List<VMSDevice> Devices;

            [XmlArray("VMSImages")]
            [XmlArrayItem("VMSIMage")]
            public VMSImage[] Images;
        }
        #endregion
        
        #region | Members |
        private List<VMSDevice> _devices;

        public List<VMSDevice> Devices
        {
            get { return _devices; }
        }
        private const int DEFAULT_PORT = 6868;
        private const int NETWORK_READ_TIMEOUT = 10000;
        private Thread ManagerThread;
        TcpListener DeviceListener;
        bool isStarted = false;
        #endregion

        #region | IDisposable |
        public void Dispose()
        {
            StopServer();
        }

        #endregion
           /*
        public VMSLayout GetLayoutFromDevice(string vmsID, int layoutID)
        {
            * 
            * 
            * 
            * 
            var dev = _devices.Select(device => device).Where(d => d.ID == vmsID).First();
            if (dev != null)
                return dev.Layouts.Select(n => n).Where(m => m.ID == layoutID).First();
            else
                return null;
        }    */
    }
}
